package br.com.linkin.mi.utils.security;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Enumeration;
import javax.net.ssl.*;

public class Security {

    static ThreadLocal<X509ExtendedKeyManager> keyManager = new ThreadLocal<X509ExtendedKeyManager>();

    /**
     * Configura um o SSLContext padrão para que não seja necessário a colocação
     * do certificado do servido no truststore e permita a configuração do
     * keyManager através da variável keyManager.
     *
     * @throws Exception
     */
    public static void configCustomManagers() throws Exception {
        TrustManager trm = new X509TrustManager() {

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            public void checkServerTrusted(X509Certificate[] certs,
                    String authType) {
            }

            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] arg0, String arg1)
                    throws CertificateException {
            }
        };
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(new KeyManager[]{new KeyManagerThreadLocalDelegate(
                    (X509ExtendedKeyManager) defaultKeyManager())},
                new TrustManager[]{trm}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    /**
     * Criar um KeyManager de acordo com as propriedades padrão.
     *
     * @return
     * @throws Exception
     */
    public static KeyManager defaultKeyManager() throws Exception {
        KeyManager km = null;
        String property = System.getProperty("javax.net.ssl.keyStorePassword");
        char[] password = null;
        if (property != null) {
            password = property.toCharArray();
        }
        String keyStoreFile = System.getProperty("javax.net.ssl.keyStore");
        if (keyStoreFile != null) {
            FileInputStream in = new FileInputStream(keyStoreFile);
            km = createKeyManager(in, password);
        }
        return km;
    }

    /**
     * create Key Manager
     *
     * @param in
     * @param password
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     */
    public static KeyManager createKeyManager(InputStream in, char[] password)
            throws NoSuchAlgorithmException, KeyStoreException, IOException,
            CertificateException, UnrecoverableKeyException {
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(in, password);
        kmf.init(ks, password);
        KeyManager[] km = kmf.getKeyManagers();
        return km[0];
    }

    /**
     * set Thread Local Key Manager
     *
     * @param keyManagerValue
     */
    public static void setThreadLocalKeyManager(
            X509ExtendedKeyManager keyManagerValue) {
        keyManager.set(keyManagerValue);
    }

    /**
     * Key Manager Thread Local Delegate
     */
    private static class KeyManagerThreadLocalDelegate extends X509ExtendedKeyManager {

        private X509ExtendedKeyManager _keyManager;

        /**
         * KeyManagerThreadLocalDelegate
         *
         * @param keyManager
         */
        public KeyManagerThreadLocalDelegate(X509ExtendedKeyManager keyManager) {
            super();
            this._keyManager = keyManager;
        }

        /**
         * X509ExtendedKeyManager
         *
         * @return
         */
        private X509ExtendedKeyManager getKeyManager() {
            X509ExtendedKeyManager retorno = keyManager.get();
            if (retorno == null) {
                retorno = _keyManager;
            }
            return retorno;
        }

        /**
         * chooseClientAlias
         *
         * @param arg0
         * @param arg1
         * @param arg2
         * @return
         */
        public String chooseClientAlias(final String[] arg0,
                final Principal[] arg1, final Socket arg2) {
            return getKeyManager().chooseClientAlias(arg0, arg1, arg2);
        }

        /**
         * chooseServerAlias
         *
         * @param arg0
         * @param arg1
         * @param arg2
         * @return
         */
        public String chooseServerAlias(final String arg0,
                final Principal[] arg1, final Socket arg2) {
            return getKeyManager().chooseServerAlias(arg0, arg1, arg2);
        }

        /**
         * getCertificateChain
         *
         * @param arg0
         * @return
         */
        public X509Certificate[] getCertificateChain(final String arg0) {
            return getKeyManager().getCertificateChain(arg0);
        }

        /**
         * getClientAliases
         *
         * @param arg0
         * @param arg1
         * @return
         */
        public String[] getClientAliases(final String arg0,
                final Principal[] arg1) {
            return getKeyManager().getClientAliases(arg0, arg1);
        }

        /**
         * getPrivateKey
         *
         * @param arg0
         * @return
         */
        public PrivateKey getPrivateKey(final String arg0) {
            return getKeyManager().getPrivateKey(arg0);
        }

        /**
         * getServerAliases
         *
         * @param arg0
         * @param arg1
         * @return
         */
        public String[] getServerAliases(final String arg0,
                final Principal[] arg1) {
            return getKeyManager().getServerAliases(arg0, arg1);
        }

        /**
         * chooseEngineClientAlias
         *
         * @param keyType
         * @param issuers
         * @param engine
         * @return
         */
        @Override
        public String chooseEngineClientAlias(String[] keyType,
                Principal[] issuers, SSLEngine engine) {
            return getKeyManager().getClientAliases(keyType[0], issuers)[0];
        }

        /**
         * chooseEngineServerAlias
         *
         * @param keyType
         * @param issuers
         * @param engine
         * @return
         */
        @Override
        public String chooseEngineServerAlias(String keyType,
                Principal[] issuers, SSLEngine engine) {
            return getKeyManager().getServerAliases(keyType, issuers)[0];
        }
    }

    /**
     * loadPkcsKeystore
     *
     * @param fis
     * @param password
     * @return
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     */
    public static KeyStore loadPkcsKeystore(InputStream fis, String password)
            throws KeyStoreException, IOException, NoSuchAlgorithmException,
            CertificateException {
        KeyStore ks = null;
        ks = KeyStore.getInstance("JKS");
        ks.load(fis, password.toCharArray());
        return ks;
    }

    /**
     * loadPkcs12KeyStore
     *
     * @param keyStore
     * @param password
     * @return
     * @throws Exception
     */
    public static KeyStore loadPkcs12KeyStore(String keyStore, String password)
            throws Exception {
        InputStream fis = new FileInputStream(keyStore);
        return loadPkcsKeystore(fis, password);
    }

    /**
     * loadPrivateKey
     *
     * @param keyStore
     * @param password
     * @return
     * @throws Exception
     */
    public static KeyStore.PrivateKeyEntry loadPrivateKey(KeyStore keyStore,
            String password) throws Exception {
        KeyStore.PrivateKeyEntry retorno = null;
        Enumeration aliasesEnum = keyStore.aliases();
        while (aliasesEnum.hasMoreElements()) {
            String alias = (String) aliasesEnum.nextElement();

            if (keyStore.isKeyEntry(alias)) {
                retorno = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, new KeyStore.PasswordProtection(
                        password.toCharArray()));
                break;
            }
        }
        return retorno;
    }

    /**
     * loadPublicKey
     *
     * @param keyStore
     * @return
     * @throws Exception
     */
    public static Key loadPublicKey(KeyStore keyStore) throws Exception {
        Key retorno = null;
        Enumeration aliasesEnum = keyStore.aliases();
        while (aliasesEnum.hasMoreElements()) {
            String alias = (String) aliasesEnum.nextElement();
            if (keyStore.isKeyEntry(alias)) {
                retorno = keyStore.getCertificate(alias).getPublicKey();
                break;
            }
        }
        return retorno;
    }

    /**
     * getNotAfter
     *
     * @param fis
     * @param password
     * @return
     * @throws Exception
     */
    public static Date getNotAfter(InputStream fis, String password)
            throws Exception {
        KeyStore ks = loadPkcsKeystore(fis, password);
        KeyStore.PrivateKeyEntry entry = loadPrivateKey(ks, password);
        X509Certificate cert = (X509Certificate) entry.getCertificate();
        return cert.getNotAfter();

    }

    /**
     * loadPkcs12PrivateKey
     *
     * @param keyStore
     * @param password
     * @return
     * @throws Exception
     */
    public static KeyStore.PrivateKeyEntry loadPkcs12PrivateKey(
            String keyStore, String password) throws Exception {
        KeyStore ks = loadPkcs12KeyStore(keyStore, password);
        return loadPrivateKey(ks, password);

    }
    private static SecureRandom random = new SecureRandom();

    /**
     * newSecureToken
     *
     * @param bits
     * @return
     */
    public static String newSecureToken(int bits) {
        return new BigInteger(bits, random).toString();
    }

    /**
     * new Secure Token
     *
     * @param bits
     * @param lenght
     * @return
     */
    public static String newSecureToken(int bits, int lenght) {
        BigInteger bI = new BigInteger(bits, random);

        DecimalFormat df = new DecimalFormat();
        df.setMinimumIntegerDigits(lenght);
        df.setMaximumIntegerDigits(lenght);

        String retorno = df.format(bI);
        String retornoFormatado = retorno.replaceAll("\\.", "");

        return retornoFormatado;
    }
}
